import cfg
import cnd
import log
import util
import re

import os
from pprint import pformat
import time
import traceback

# TODO - ftp step
# TODO - oracle step
# TODO - excel step
# TODO - COM/DDE/OLE step

by_kind = {} # kind -> Step

def load(name,cfg,job):
	# print "LOAD",name,cfg,job # xxx
	kind=cfg['kind']
	if kind not in by_kind:
		cfg['macro']=kind
		cfg['kind']='macro'
		kind='macro'
	return by_kind[kind](name,cfg,job)

def register(kind,cls):
	cls.kind=kind
	by_kind[kind]=cls

class Step:
	def __init__(self,name,cfg,job):
		self.name = name.lower()
		self.cfg = cfg
		self.job = job
		self.init()
	def init(self):
		pass # TODO
	def run(self):
		self.log_path=self.job.get_step_log_path(self.name)
		os.chdir(self.job.outdir)
		return 0 # TODO
	def cfg_get(self,key,default=''):
		key = util.template(key,**self.job.variables)
		val = self.cfg.get(key,default)
		return util.template(val,**self.job.variables)
	def list_vars(self):
		names = set()
		for k in self.cfg.keys():
			match = re.findall("^(var[0-9]+)[.]name",k)
			if match:
				v = match[0]
				names.add(v)
		return list(sorted(names))
	def should_skip(self):
		if cnd.exists(self.job.name+'.'+self.kind+'.skip',self.job.date):
			self.job.log.info('SKIP STEP %s (because of skip kind condition)',self.name)
			return True
		if cnd.exists(self.job.name+'.'+self.name+'.skip',self.job.date):
			self.job.log.info('SKIP STEP %s (because of skip step condition)',self.name)
			return True
		value = self.cfg_get('skip.if.value')
		match = self.cfg_get('skip.if.match')
		if value==match=="":
			skip = False
		else:
			skip = bool(re.findall("^(?:"+match+")$",value))
			self.job.log.debug('%s skip=%s v="%s" m="%s"',self.name,skip,value,match)
		if skip:
			self.job.log.info('SKIP STEP %s (because of skip.if)',self.name)
		# handle "only" condition
		value = self.cfg_get('only.if.value')
		match = self.cfg_get('only.if.match')
		if value==match=="":
			only = True
		else:
			only = bool(re.findall("^(?:"+match+")$",value))
			self.job.log.debug('%s only=%s v="%s" m="%s"',self.name,only,value,match)
		if not only:
			self.job.log.info('SKIP STEP %s (because of only.if)',self.name)
		out = skip or not only
		return out
	def log_data(self,data):
		f=open(self.log_path,"a")
		f.write(time.strftime('%Y-%m-%d;%H:%M:%S')+"\n")
		f.write(pformat(data)+"\n\n")

import sas
class SASStep(Step):
	def init(self):
		#print "SAS step init", self.name
		pass
	def macro_var(self,name,query,default=''):
		outpath=self.job.get_out_path("var_%(name)s.txt" % locals())
		return """
			* export macro variable %(name)s;
			proc sql outobs=1;
				create table _var_%(name)s  as
				%(query)s;
			run;
			proc export data=_var_%(name)s outfile="%(outpath)s"
				   DBMS=DLM replace;
				DELIMITER=';';
			run;
		""" % locals()
	def run(self):
		Step.run(self)
		if self.should_skip(): return 0
		print "SAS step run", self.name
		code = self.cfg_get('code')
		path = self.cfg_get('path') # NOTUSED ???
		before = self.cfg_get('before','')
		after = self.cfg_get('after','')
		run_before="""%%let DATE=%%sysfunc(inputn(%s,yymmdd8.));\n"""%self.job.date
		run_before+=before
		run_after=""+after
		#self.log.debug('cfg -> %s',self.cfg)
		#self.log.debug('list_vars -> %s',self.list_vars())
		for v in self.list_vars():
			name = self.cfg_get(v+'.name')
			query = self.cfg_get(v+'.query')	   
			run_after += self.macro_var(name,query)
		log_path=self.log_path
		rc = 0
		if code:
			try:
				print "code",code
				code_path=cfg.get_out_path(self.job.date, self.job.name, self.name+'.sas')
				out_path=cfg.get_out_path(self.job.date, self.job.name, self.name+'.txt')
				open(code_path,"w").write(run_before+code+"\n"+run_after)
				errors = sas.run_batch(code_path,log_path,out_path)
				if errors:
					rc = 2
					self.job.log.error("%s SASStep %s",self.name,errors[0].strip())
				for v in self.list_vars():
					name = self.cfg_get(v+'.name')
					default = self.cfg_get(v+'.default')
					try:
						outpath=self.job.get_out_path("var_%(name)s.txt" % locals(),remove=False)
						lines = open(outpath,"r").readlines(64000)
						value = lines[1].split(";")[0].strip()
					except Exception,e:
						value = default
						#self.job.log.error("%s SASStep var %s %s",self.name,name,e) # TEGO NIE LOGUJEMY???
					self.job.variables[name]=value
					self.job.log.info("MACRO VARIABLE %s SET TO '%s'",name,value)
			except Exception,e:
				self.job.log.error("%s SASStep %s",self.name,e)
				rc = 1
		return rc
register('sas',SASStep)

import egp
class EGP1Step(Step):
	def init(self):
		#print "EGP step init", self.name
		pass
	def macro_var(self,name,query,default=''):
		return """
			* export macro variable %(name)s;
			%%let _var_%(name)s=%(default)s;
			proc sql outobs=1;
				create table _var_%(name)s  as
				%(query)s;
			run;
			proc sql outobs=1;
				select * into :_var_%(name)s from _var_%(name)s;
			run;
			%%put DEBUG: _var_%(name)s value >>>%%trim(&_var_%(name)s)<<<;
		""" % locals()
	def run(self):
		Step.run(self)
		if self.should_skip(): return 0
		print "EGP step run", self.name
		code = self.cfg_get('code','')
		path = self.cfg_get('path','')
		listname = self.cfg_get('listname','')
		before = self.cfg_get('before','')
		after = self.cfg_get('after','')
		run_before="""%%let DATE=%%sysfunc(inputn(%s,yymmdd8.));\n"""%self.job.date
		run_before+=before
		log_path=self.log_path
		run_after=""+after
		for v in self.list_vars():
			name = self.cfg_get(v+'.name')
			query = self.cfg_get(v+'.query')
			default = self.cfg_get(v+'.default','')
			run_after += self.macro_var(name,query,default)
		err = 1
		if code:
			try:
				err = egp.run_str(code,run_before=run_before,run_after=run_after,log_path=log_path)
			except Exception,e:
				exc = "|".join(traceback.format_exc().split("\n"))
				self.job.log.error("%s %s",self.name,exc)
		if path:
			try:
				if listname:
					err = egp.run_ordered_list(path,listname,run_before=run_before,run_after=run_after,log_path=log_path)
				else:
					err = egp.run_all_ordered_lists(path,run_before=run_before,run_after=run_after,log_path=log_path)
			except Exception,e:
				exc = "|".join(traceback.format_exc().split("\n"))
				self.job.log.error("%s EGPStep %s",self.name,exc)
		if self.list_vars():
			logdata = open(log_path,'r').read()
			m = re.findall("DEBUG: _var_(.+) value >>>([^<&]*)<<<",logdata)
			if m:
				for name,value in m:
					self.job.variables[name] = value
					self.job.log.info("MACRO VARIABLE %s SET TO '%s'",name,value)
		return err
register('egp1',EGP1Step)

class EGPStep(Step):
	def init(self):
		#print "EGP step init", self.name
		pass
	def macro_var(self,name,query,default=''):
		return """
			* export macro variable %(name)s;
			%%let _var_%(name)s=%(default)s;
			proc sql outobs=1;
				create table _var_%(name)s  as
				%(query)s;
			run;
			proc sql outobs=1;
				select * into :_var_%(name)s from _var_%(name)s;
			run;
			%%put DEBUG: _var_%(name)s value >>>%%trim(&_var_%(name)s)<<<;
		""" % locals()
	def run(self):
		Step.run(self)
		if self.should_skip(): return 0
		print "EGP step run", self.name
		path = self.cfg_get('path','')
		listname = self.cfg_get('listname','.')
		before = self.cfg_get('before','')
		after = self.cfg_get('after','')
		run_before="""%%let DATE=%%sysfunc(inputn(%s,yymmdd8.));\n"""%self.job.date
		run_before+=before
		log_path=self.log_path
		run_after=""+after
		for v in self.list_vars():
			name = self.cfg_get(v+'.name')
			query = self.cfg_get(v+'.query')
			default = self.cfg_get(v+'.default','')
			run_after += self.macro_var(name,query,default)
		cmd_rc=100
		if path:
			try:
				if run_before:
					rb_path = self.job.get_out_path(self.name+'_rb.sas')
					open(rb_path,'w').write(run_before)
				else:
					rb_path = "."
				if run_after:
					ra_path = self.job.get_out_path(self.name+'_ra.sas')
					open(ra_path,'w').write(run_after)
				else:
					ra_path = "."
				args = "%s %s %s %s %s" % (path,log_path,listname,rb_path,ra_path)
				cmd = "python "+os.path.join(cfg.src_root_dir,'egp.py')+" "+args
				cmd_rc = os.system(cmd)
			except Exception,e:
				exc = "|".join(traceback.format_exc().split("\n"))
				self.job.log.error("%s EGPStep %s",self.name,exc)
		if self.list_vars():
			logdata = open(log_path,'r').read()
			m = re.findall("DEBUG: _var_(.+) value >>>([^<&]*)<<<",logdata)
			if m:
				for name,value in m:
					self.job.variables[name] = value
					self.job.log.info("MACRO VARIABLE %s SET TO '%s'",name,value)
		return cmd_rc
register('egp',EGPStep)

class OrderStep(Step):
	def init(self):
		#print "Order step init", self.name
		pass
	def run(self):
		Step.run(self)
		if self.should_skip(): return 0
		from sch import order_all, order
		from cal import offset
		date_offset=self.cfg_get('date','0')
		rerun=self.cfg_get('rerun','no')
		jobs=[j.strip().lower() for j in self.cfg_get('jobs','').split()]
		print "OrderStep",self.job.date,date_offset,jobs
		d = offset(self.job.date,date_offset)
		if jobs==[]:
			order_all(d)
		else:
			do_rerun = rerun.lower() not in ('no','false','0','')
			order(d,jobs,do_rerun)
		return 0
register('order',OrderStep)

class MailStep(Step):
	def run(self):
		Step.run(self)
		if self.should_skip(): return 0
		import mail
		to		= self.cfg_get('to')
		cc		= self.cfg_get('cc')
		bcc	  = self.cfg_get('bcc')
		body	= self.cfg_get('body')
		sender  = self.cfg_get('sender')
		subject = self.cfg_get('subject')
		files = self.cfg_get('files')
		images = self.cfg_get('images') 
		charset = self.cfg_get('charset','utf-8')
		html = self.cfg_get('html','1')
		path = self.cfg_get('path').strip()
		to = ";".join(re.findall('[A-Za-z0-9._-]+@[A-Za-z0-9._-]+',to))
		cc = ";".join(re.findall('[A-Za-z0-9._-]+@[A-Za-z0-9._-]+',cc))
		bcc = ";".join(re.findall('[A-Za-z0-9._-]+@[A-Za-z0-9._-]+',bcc))
		files = [f.strip() for f in re.split("\n|;",files)]
		images = [f.strip() for f in re.split("\n|;",images)]
		if re.findall("no|false|0|off",html.lower()):
			html = False
		else:
			html = True
		if path:
			body=open(path,'r').read()
		try:
			mail.send(to,subject,body,sender,cc=cc,bcc=bcc,html=html,charset=charset,files=files,images=images)
			self.log_data(locals())
		except Exception,e:
			self.log_data(locals())
			self.log_data(e)
			return 1
		# TODO status wysylki
		# TODO logowanie
		# TODO reszta zadan z mail.py
		return 0
register('mail',MailStep)

class CmdStep(Step):
	def init(self):
		#print "CmdStep init", self.name
		pass
	def run(self):
		Step.run(self)
		if self.should_skip(): return 0
		cmd = "cmd /C " + self.cfg_get('cmd') + " 2>&1 >%s" % self.log_path
		rcok = self.cfg_get('rcok','0')
		rcvar = self.cfg_get('rcvar','').lower()
		cd=self.cfg_get('dir',self.job.outdir)
		os.chdir(cd)
		cmd_rc = os.system(cmd)
		if re.findall('^(?:'+rcok+')$',str(cmd_rc)):
			rc = 0
		else:
			rc =1
		if rcvar:
			self.job.variables[rcvar]=str(cmd_rc)
			self.job.log.info("MACRO VARIABLE %s SET TO '%s'",rcvar,cmd_rc)
		self.log_data(locals())
		return rc
register('cmd',CmdStep)

class ErrorStep(Step):
	def init(self):
		#print "ErrorStep init", self.name
		pass
	def run(self):
		Step.run(self)
		if self.should_skip(): return 0
		return 999
register('error',ErrorStep)

class SleepStep(Step):
	def init(self):
		#print "SleepStep init", self.name
		pass
	def run(self):
		Step.run(self)
		if self.should_skip(): return 0
		return -999
register('sleep',SleepStep)

class DummyStep(Step):
	def run(self):
		Step.run(self)
		if self.should_skip(): return 0
		return 0
register('dummy',DummyStep)

class WriteStep(Step):
	def init(self):
		#print "WriteStep init", self.name
		pass
	def run(self):
		Step.run(self)
		if self.should_skip(): return 0
		mode = self.cfg_get('mode','w')
		path = self.cfg_get('path')
		body = self.cfg_get('body')
		self.job.log.debug("WriteStep mode=%s path=%s bytes=%d",mode,path,len(body))
		open(path,mode).write(body)
		return 0
register('write',WriteStep)

# TODO - line number
class ScanStep(Step):
	def init(self):
		#print "ScanStep init", self.name
		pass
	def run(self):
		Step.run(self)
		if self.should_skip(): return 0
		path = self.cfg_get('path')
		var = self.cfg_get('var')
		pattern = self.cfg_get('pattern')
		r = re.compile(pattern)
		f = open(path,"r")
		m = []
		first_line=0
		for i,line in enumerate(f.readlines()):
			x=r.findall(line)
			if x:
				m += [x]
				if first_line==0:
					first_line=i+1
		self.job.variables[var+"_cnt"] = str(len(m))
		if m:
			self.job.variables[var] = m[0][0]
			self.job.variables[var+"_line"] = str(first_line)
		else:
			self.job.variables[var] = ""
			self.job.variables[var+"_line"] = ""
		self.log_data(m)
		self.job.log.info("MACRO VARIABLE %s SET TO '%s'",var+"_line",self.job.variables[var+"_line"])
		self.job.log.info("MACRO VARIABLE %s SET TO '%s'",var+"_cnt",self.job.variables[var+"_cnt"])
		self.job.log.info("MACRO VARIABLE %s SET TO '%s'",var,self.job.variables[var])
		return 0
register('scan',ScanStep)

class ScanStep2(Step):
	def init(self):
		#print "ScanStep init", self.name
		pass
	def run(self):
		Step.run(self)
		if self.should_skip(): return 0
		path = self.cfg_get('path')
		var = self.cfg_get('var')
		pattern = self.cfg_get('pattern')
		index = int(self.cfg_get('index','1'))
		r = re.compile(pattern)
		f = open(path,"r")
		match_list = []
		line_list = []
		for i,line in enumerate(f.readlines()):
			x=r.findall(line)
			if x:
				match_list += [x]
				line_list += [i+1]
		self.job.variables[var+"_cnt"] = str(len(match_list))
		try:
			self.job.variables[var] = match_list[index][0]
			self.job.variables[var+"_line"] = str(line_list[index][0])
		except:
			self.job.variables[var] = ""
			self.job.variables[var+"_line"] = ""
		self.log_data(m)
		self.job.log.info("MACRO VARIABLE %s SET TO '%s'",var+"_line",self.job.variables[var+"_line"])
		self.job.log.info("MACRO VARIABLE %s SET TO '%s'",var+"_cnt",self.job.variables[var+"_cnt"])
		self.job.log.info("MACRO VARIABLE %s SET TO '%s'",var,self.job.variables[var])
		return 0
register('scan2',ScanStep2)

class PythonStep(Step):
	def run(self):
		Step.run(self)
		if self.should_skip(): return 0
		code_path = self.cfg_get('path','')
		code = self.cfg_get('code','')
		if code_path:
			code = open(code_path,'r').read()
		c = compile(code,self.job.name+'_'+self.name+'.py','exec')
		exec(c)
		for v in self.list_vars():
			name = self.cfg_get(v+'.name')
			default = self.cfg_get(v+'.default')
			expr = self.cfg_get(v+'.expr')
			try:
				value = eval(expr)
			except Exception,e:
				value = default
			self.job.variables[name]=value
			self.job.log.info("MACRO VARIABLE %s SET TO '%s'",name,value)
		return 0
register('python',PythonStep)

class MacroStep(Step):
	def run(self):
		Step.run(self)
		if self.should_skip(): return 0
		macro = self.cfg_get('macro','')
		p = os.path.join(cfg.job_root_dir,'macro',macro+'.ini')
		if not os.path.exists(p):
			return 11
		cp = cfg.Parser()
		v={}
		v.update(self.job.variables)
		v.update(self.cfg)
		cp.read(p,**v)
		if 'macro' not in cp.sections():
			return 12
		new_cfg = dict(cp.items('macro'))
		step=load(self.name,new_cfg,self.job)
		return step.run()
register('macro',MacroStep)

if __name__=="__main__":
	pass
